﻿#ifndef DECODER_H
#define DECODER_H

#include <QObject>
#include <QUrl>
#include <QThread>
#include <queue>
#include <QSemaphore>
#include <mutex>
#include "PacketQueue.h"
#include <memory>
#include <QUrl>
#include <QSize>
#include <list>
#include <QSemaphore>
#include "FrameQueue.h"
#include <QDateTime>

extern "C"
{
#include <libavcodec/avcodec.h>
}

struct AVPacket;
struct AVCodecContext;
struct AVFrame;
struct AVFormatContext;
struct AVStream;
struct AVCodec;
class IFrameFilter;

class Decoder : public QObject
{
    Q_OBJECT

    Q_PROPERTY(bool isEnding READ isEnding NOTIFY isEndingChanged)

    Q_PROPERTY(bool isRunning READ isRunning NOTIFY isRunningChanged)

    Q_PROPERTY(int nbAudioStreams READ nbAudioStreams)

    Q_PROPERTY(int decodedAudioFrameCount READ decodedAudioFrameCount)

    Q_PROPERTY(int decodedVideoFrameCount READ decodedVideoFrameCount)

public:
    explicit Decoder(QObject *parent = nullptr);

    ~Decoder();

    /**
     * @brief videoFile 获取当前正在解码的文件名
     * @return
     */
    Q_INVOKABLE QString videoFile();

    Q_INVOKABLE double fileDuration();

    Q_INVOKABLE void seek(double timePoint);

    Q_INVOKABLE bool start(const QUrl& file);

    Q_INVOKABLE void setVideoFrameFormat(QSize size, AVPixelFormat fmt);

    Q_INVOKABLE int videoFrameQueueSize() const { return m_videoFrameQueueSize; }

    Q_INVOKABLE void setVideoFrameQueueSize(int size);

    int decodedAudioFrameCount();

    int decodedVideoFrameCount();

    DecodecSerial decodecSerial() const;

    Q_INVOKABLE int64_t serialId() const {
        return decodecSerial().id;
    }

    Q_INVOKABLE int64_t serialTimepoint() {
        return decodecSerial().timepoint;
    }

    bool isEnding() const;

    bool isRunning() const;

    AVRational videoTimebase();

    AVFrame* nextVideoFrame(int timeout = 100);

    int videoFrameQueueSize();

    AVFrame* nextAudioFrame();

    int audioFrameQueueSize();

    int nbAudioStreams() { return m_nbAudioStreams; }

public slots:
    void stop();

    void dumpMediaInformation();

signals:
    void decodingError(QString desc);

    void decodecSerialChanged();

    void isEndingChanged();

    void isRunningChanged();

private:

    bool initDecodecContexts();

    void setIsEnding(bool b);
    void setIsRunning(bool b);

    struct DecodecContext
    {
        int streamIndex = -1;
        AVStream* stream = nullptr;
        AVCodec* codec = nullptr;
        AVCodecContext* codecCtx = nullptr;
        QThread* td = nullptr;
        std::unique_ptr<PacketQueue> pktQueue;
        std::unique_ptr<FrameQueue> frameQueue;
        std::unique_ptr<IFrameFilter> frameFilter;
    };

    void readThread();

    void decodecThread(int streamIndex);

    QThread *m_readThread = nullptr;

    int seekToPreviousKeyframe(AVFormatContext* fmtCtx, int baseStreamIndex, double position, double& result);

    int openCodecContext(DecodecContext& ctx);

    QString m_stateDesc;

    QString m_file;

    AVFormatContext* m_fmtCtx = nullptr;
    std::vector<DecodecContext> m_decCtxs;
    int m_baseStreamIndex = -1;
    int m_videoStreamIndex = -1;
    int m_audioStreamIndex = -1;
    int m_nbAudioStreams = 0;

    double m_fileDuration = 0.0;

    std::atomic_int32_t m_videoFrameQueueSize = 5;

    QSize m_videoFrameSize;
    AVPixelFormat m_videoFrameFormat = AV_PIX_FMT_YUV420P;

    int64_t m_currPTS = 0;

    bool m_isEnding = false;

    bool m_isRunning = false;
};
#endif // DECODER_H
